﻿using System;
using System.ComponentModel;
using System.Runtime.Serialization;

using SharpGL.Media3D;
using SharpGL.SceneGraph.Quadrics;

namespace SharpGL.SceneGraph.Primitives
{
    /// <summary>
    /// a solid polyline connect a set of vectors by cylinder.
    /// </summary>
    [DataContract(IsReference = true)]
    public class SolidPolyline : Polyline, IHasOpenGLContext
    {
        #region Private Fields

        /// <summary>
        /// Current OpenGL context
        /// </summary>
        private OpenGL currentOpenGLContext = null;

        /// <summary>
        /// The object (cylinder) used for segment drawing.
        /// </summary>
        [DataMember()]
        private Cylinder segmentObject = new Cylinder();

        #endregion Private Fields

        #region Protected Methods

        protected virtual void OnCreateInContext(OpenGL gl)
        {
            segmentObject.CreateInContext(gl);
        }

        protected virtual void OnDestroyInContext(OpenGL gl)
        {
            segmentObject.DestroyInContext();
        }

        protected virtual void RenderSegmentObject(OpenGL gl, RenderMode renderMode, Point3D firstVertex, Point3D secondVertex)
        {
            if (OpenGL.IsValidContext(gl))
            {
                Vector3D baseVector = new Vector3D(0.0f, 0.0f, 1.0f);
                Vector3D segmentVector = secondVertex - firstVertex;
                Vector3D rotateVector = Vector3D.CrossProduct(baseVector, segmentVector);
                double angle = Vector3D.AngleBetween(baseVector, segmentVector);

                float[] currentLineWidth = new float[] { 0.0f };

                gl.GetFloat(OpenGL.GL_LINE_WIDTH, currentLineWidth);

                segmentObject.TopRadius = currentLineWidth[0] / 2.0f;
                segmentObject.BaseRadius = currentLineWidth[0] / 2.0f;
                segmentObject.Height = segmentVector.Length;

                gl.PushMatrix();

                gl.Translate(firstVertex.X, firstVertex.Y, firstVertex.Z);
                gl.Rotate(angle, rotateVector.X, rotateVector.Y, rotateVector.Z);

                segmentObject.Render(gl, renderMode);

                gl.PopMatrix();
            }
        }

        protected override void InternalRender(OpenGL gl, RenderMode renderMode)
        {
            ValidateRenderingContext(gl);

            if (OpenGL.IsValidContext(gl))
            {
                if (Vertices != null && Vertices.Count >= 2)
                {
                    Point3D firstVertex;
                    Point3D secondVertex;

                    //  Get the first point of segment.
                    firstVertex = Vertices[0];
                    for (int vertexIndex = 1; vertexIndex < Vertices.Count; vertexIndex++)
                    {
                        //  Get the second point of segment.
                        secondVertex = Vertices[vertexIndex];
                        //  Render segment.
                        RenderSegmentObject(gl, renderMode, firstVertex, secondVertex);
                        //  The second point is the first for next segment.
                        firstVertex = secondVertex;
                    }
                }
            }
        }

        public void ValidateCreationContext(OpenGL gl)
        {
            if (!OpenGL.Equals(gl, currentOpenGLContext))
            {
                if (OpenGL.IsValidContext(currentOpenGLContext))
                    throw new Exception("Cannot create '" +
                                        Name +
                                        "' in context '" +
                                        (OpenGL.IsValidContext(gl) ? gl.Renderer : "Null") +
                                        "' - it is already created in '" +
                                        (OpenGL.IsValidContext(currentOpenGLContext) ? currentOpenGLContext.Renderer : "Null") +
                                        "' OpenGL context.");

                if (!OpenGL.IsValidContext(gl))
                    throw new Exception("Cannot create '" +
                                        Name +
                                        "' in illegal OpenGL context.");
            }
        }

        public void ValidateRenderingContext(OpenGL gl)
        {
            if (!OpenGL.Equals(gl, currentOpenGLContext))
                throw new Exception("Cannot render '" +
                                    Name +
                                    "' in context '" +
                                    (OpenGL.IsValidContext(gl) ? gl.Renderer : "Null") +
                                    "' - it is created in '" +
                                    (OpenGL.IsValidContext(currentOpenGLContext) ? currentOpenGLContext.Renderer : "Null") +
                                    "' OpenGL context.");
        }

        #endregion Protected Methods

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SolidPolyline"/> class.
        /// </summary>
        public SolidPolyline() 
		{
			Name = "Solid polyline";

            segmentObject.QuadricDrawStyle = DrawStyle.Fill;
            segmentObject.NormalOrientation = Orientation.Outside;
            segmentObject.NormalGeneration = Normals.Smooth;
            segmentObject.TextureCoords = false;
            segmentObject.Slices = 3;
            segmentObject.Stacks = 1;
		}

        #endregion Constructors

        #region Public Methods

        /// <summary>
        /// Create in the context of the supplied OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public virtual void CreateInContext(OpenGL gl)
        {
            ValidateCreationContext(gl);

            currentOpenGLContext = gl;

            // Call on create in OpenGL context function.
            OnCreateInContext(gl);

            NotifyContextChanged(this, new ContextChangeEventArgs(ContextChangeAction.CreateInContext));
        }

        /// <summary>
        /// Destroy in the current OpenGL instance.
        /// </summary>
        public virtual void DestroyInContext()
        {
            if (OpenGL.IsValidContext(currentOpenGLContext))
            {
                NotifyContextChanged(this, new ContextChangeEventArgs(ContextChangeAction.DestroyInContext));

                // Call on destroy in OpenGL context function.
                OnDestroyInContext(currentOpenGLContext);

                currentOpenGLContext = null;
            }
        }

        #endregion Public Methods

        #region Public Properties

        /// <summary>
        /// Current OpenGL context
        /// </summary>
        [Browsable(false)]
        public OpenGL CurrentOpenGLContext
        {
            get { return currentOpenGLContext; }
            protected set { currentOpenGLContext = value; }
        }

        /// <summary>
        /// The slices.
        /// </summary>
        public int Slices
        {
            get { return segmentObject.Slices; }
            set
            {
                segmentObject.Slices = value;

                NotifyPropertyChanged("Slices");
            }
        }

        /// <summary>
        /// The stacks.
        /// </summary>
        public int Stacks
        {
            get { return segmentObject.Stacks; }
            set
            {
                segmentObject.Stacks = value;

                NotifyPropertyChanged("Stacks");
            }
        }

        #endregion Public Properties
    }
}
